CTF QEMU 虚拟机逃逸总结

不知什么来源的题目

来源:https://kirin-say.top/2019/11/06/QEMU-Escape-in-Cloud-Security-Game/

漏洞:也不能说漏洞,在mmio_read里面直接给了system的后门

1
2
3
4
5
6
7
8
9
10
11
12
signed __int64 __fastcall rfid_read(__int64 a1, unsigned __int64 a2)
{
size_t v2; // rax

if ( ((a2 >> 20) & 0xF) != 15 )
{
v2 = strlen(magic_code);
if ( !memcmp(code, magic_code, v2) )
system(command);
}
return 0x42066LL;
}

利用:通过rfid_write将code赋值为跟magic_code相等,再设置一下command,最后调用rfid_read即可完成逃逸

BlizzardCTF 2017 Strng

题目链接:https://github.com/rcvalle/blizzardctf2017

漏洞:在pmio中对于数组的索引没有限制,导致越界读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
uint64_t __fastcall strng_pmio_read(STRNGState *opaque, hwaddr addr, unsigned int size)
{
uint64_t result; // rax
uint32_t v4; // edx

result = -1LL;
if ( size == 4 )
{
if ( addr )
{
if ( addr == 4 )
{
v4 = opaque->addr;
if ( !(v4 & 3) )
result = opaque->regs[v4 >> 2]; <========= 越界读
}
}
else
{
result = opaque->addr;
}
}
return result;
}

void __fastcall strng_pmio_write(STRNGState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
if ( size == 4 )
{
if ( addr )
{
if ( addr == 4 )
{
v4 = opaque->addr;
if ( !(v4 & 3) )
{
v5 = v4 >> 2;
if ( (_DWORD)v5 == 1 )
{
......
......
}
else
{
opaque->regs[v5] = val; <========越界写
}
}
}
}
......
......
}
}

利用:
1、通过数组越界,获得任意读写来泄露函数指针,计算出system函数地址
2、之后改写rand_r函数指针为system,调用rand_r函数即可劫持控制流

HITB-GSEC-2017-babyqemu

题目下载:https://github.com/kitctf/writeups/blob/master/hitb-gsec-2017/babyqemu/babyqemu.tar.gz

漏洞:也是对于dma_buf数组的索引没有限制,导致任意地址读写,只不过这个题目需要我们将虚拟地址转化为物理地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void __fastcall hitb_dma_timer(HitbState *opaque)
{
dma_addr_t v1; // rax
__int64 v2; // rdx
uint8_t *v3; // rsi
dma_addr_t v4; // rax
dma_addr_t v5; // rdx
uint8_t *v6; // rbp
uint8_t *v7; // rbp

v1 = opaque->dma.cmd;
if ( v1 & 1 )
{
if ( v1 & 2 )
{
v2 = (unsigned int)(LODWORD(opaque->dma.src) - 0x40000);
if ( v1 & 4 )
{
v7 = (uint8_t *)&opaque->dma_buf[v2];
((void (__fastcall *)(uint8_t *, _QWORD))opaque->enc)(v7, LODWORD(opaque->dma.cnt));
v3 = v7;
}
else
{
v3 = (uint8_t *)&opaque->dma_buf[v2]; <=======越界读
}
cpu_physical_memory_rw(opaque->dma.dst, v3, opaque->dma.cnt, 1); //读出来后传递给dma.dst
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
else
{
v6 = (uint8_t *)&opaque[0xFFFFFFDBLL].dma_buf[(unsigned int)opaque->dma.dst + 0x510]; //这里ida有点问题,其实这里是opaque->dma_buf[opaque->dma.dst-0x40000]
LODWORD(v3) = (_DWORD)opaque + opaque->dma.dst - 0x40000 + 0xBB8;
cpu_physical_memory_rw(opaque->dma.src, v6, opaque->dma.cnt, 0); // 越界写opaque->dma_buf[opaque->dma.dst-0x40000]
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
if ( opaque->dma.cmd & 4 )
{
v3 = (uint8_t *)LODWORD(opaque->dma.cnt);
((void (__fastcall *)(uint8_t *, uint8_t *, dma_addr_t))opaque->enc)(v6, v3, v5);
v4 = opaque->dma.cmd;
v5 = opaque->dma.cmd & 4;
}
}
opaque->dma.cmd = v4 & 0xFFFFFFFFFFFFFFFELL;
if ( v5 )
{
opaque->irq_status |= 0x100u;
hitb_raise_irq(opaque, (uint32_t)v3);
}
}
}

利用:
1、通过数组越界读泄露函数指针enc,由于这个qemu-system-x86_64的导入表有system,所以我们直接可以算出system
2、用system覆盖enc函数
3、写入opaque->dma_buf为要执行的命令,比如cat flag
4、使用cmd=1|2|4时,调用enc函数,劫持控制流

QEMU Escape — vm_escape from 0CTF 2017 Finals

没有公开下载的题目
wp: https://blog.eadom.net/writeups/qemu-escape-vm-escape-from-0ctf-2017-finals-writeup/

漏洞:
1、对于phys_mem_write的len没有限制,导致可以越界读,同样用到了物理地址(需要将虚拟地址转化为物理地址)
2、uaf漏洞,在vdd_mmio_write中可以设置dma_timer的过期时间,而在vdd_dma_timer中调用了opaque->dma_state->phys_mem_read/write,而pci_vdd_uninit函数会将dma_state释放;所以加入pci_vdd_uninit函数,在vdd_dma_timer之前调用,就是uaf了。

利用:
1、通过phys_mem_write的越界读泄露程序地址还有libc地址(其实这里libc地址用不到,因为导入表有system了,只要程序地址加上偏移就行)
2、将我们要执行的命令复制到opaque->dma_buf
3、在vdd_mmio_write中可以设置dma_timer的过期时间
4、往/sys/bus/pci/slots/4/power写入0,从而触发pci_vdd_uninit去free dma_state
5、通过vdd_linear_write申请dma_state同样大小的内存,并用正确的值填充,具体代码如下,主要是将cmd设置为2,而phys_mem_read函数指针,设置为system,最后上面的dma_timer时间到了就会调用opaque->dma_state->phys_mem_read,从而让我们劫持控制流

1
2
3
4
5
6
7
8
9
10
11
void put_fake_dma(void)
{
struct dma fakedma;
fakedma.cmd = 2;
fakedma.phys_mem_read = system_addr;
memcpy(pbuf, (void *)&fakedma, sizeof(fakedma));
set_dmalen(0x330);
set_dmastate_src(virt_to_phys(pbuf));
set_dmastate_dst(virt_to_phys(pbuf));
outb(0, VDB_PORT + 0);
}

Defcon 2018 - EC3

题目:https://github.com/o-o-overflow/chall-ec-3

漏洞:在mmio_write中,free的时候,没有将指针置空,导致了uaf漏洞,就跟常规的堆题一样,使用fastbin attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void __fastcall ooo_mmio_write_6E61F4(__int64 opaque, __int64 addr, __int64 value, unsigned int size)
{
......
......
choose = ((unsigned int)addr & 0xF00000) >> 20;
switch ( choose )
{
case 1u:
free(gbuf_bss_1317940[((unsigned int)addr_copy & 0xF0000) >> 16]);
break;
case 2u:
v12 = ((unsigned int)addr_copy & 0xF0000) >> 16;
v8 = addr_copy;
memcpy((char *)gbuf_bss_1317940[v12] + (signed __int16)addr_copy, &value_point[4], size);
break;
case 0u:
v11 = ((unsigned int)addr_copy & 0xF0000) >> 16;
if ( v11 == 15 )
{
for ( i = 0; i <= 14; ++i )
gbuf_bss_1317940[i] = malloc(8LL * *(_QWORD *)&value_point[4]);
}
else
{
gbuf_bss_1317940[v11] = malloc(8LL * *(_QWORD *)&value_point[4]);
}
break;
}
}

利用:
1、在gbuf_bss_1317940中构0x7f绕过fastbin检查,比如在gbuf_bss_1317940[8]中存放malloc的返回值
2、利用fastbin attack获得指向gbuf_bss_1317940的指针
3、利用上面的指针,将free或者malloc的got地址写入到gbuf_bss_1317940[10]中
4、通过edit gbuf_bss_1317940[10]即可修改got表
5、最后调用free或者malloc即可劫持控制流,这里有个cat flag的后门函数,劫持到那即可

题目难点在于没有符号表,需要逆向,还有就是qemu自身也会申请或者释放0x60大小的堆,所以需要循环申请和循环修改提升成功率

强网杯 qwb 2019 final execchrome

题目:https://github.com/ray-cp/vm-escape/tree/master/qemu-escape/qwb-final-2019-ExecChrome

只有qemu-system-x86_64,可能需要修复需要的lib,需要的库超级多

漏洞:
1、 nvme_mmio_read存在越界读漏洞,也是对索引没有限制

1
2
3
4
5
6
7
uint64_t __cdecl nvme_mmio_read(NvmeCtrl *opaque, hwaddr addr, unsigned int size)
{
......
......
memcpy(&val, &ptr[addr], sizea);
return val;
}

2、 在nvme_mmio_write中的nvme_write_bar函数中故意添加了代码,也是导致NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ( sizea == 2 )
{
*(_WORD *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
}
else if ( sizea > 2 )
{
if ( sizea == 4 )
{
*(_DWORD *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
}
else if ( sizea == 8 )
{
*(uint64_t *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
}
}
else if ( sizea == 1 )
{
*((_BYTE *)&NvmeCtrl->bar.cap + offset) = dataa;
}

漏洞利用:
1、利用nvme_mmio_read进行程序地址以及堆地址的泄露,got表中存在system,所以程序地址加一个偏移就得到了system got的地址
2、通过NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写,在bar后面的地址伪造一个admin_sq->timer,timer中的cb设置为system got的地址
3、也是NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写,修改admin_cq->timer指针,指向上面伪造的timer

最后竟然通过只有通过重启动触发timer的调用…

XNUCA 2018 SSD

题目:https://github.com/w0lfzhang/vmescape/tree/master/xnuca

漏洞:还是常规的fastbin的uaf放到qemu里面,free的时候没将指针置空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
xnucaState *__fastcall xnuca_timer(xnucaState *State)
{
xnucaState *result; // rax
int v2; // eax
void **v3; // rbx

result = (xnucaState *)(State->cmd_9D0 & 4);
if ( (_DWORD)result )
{
v2 = State->choose_9EC;
switch ( v2 )
{
case 2:
*(_DWORD *)((unsigned int)State->mallocSize + *(_QWORD *)(State->heaplist_9E0 + 8LL * (unsigned int)State->index)) = State->value_9F8;
break;
case 3:
free(*(void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->index));
break;
case 1:
v3 = (void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->index);
*v3 = malloc((unsigned int)State->mallocSize);
break;
}
result = State;
State->cmd_9D0 &= 0xFFFFFFFB;
}
return result;
}

漏洞利用:
通过fastbin attack伪造fd指向free got后,修改free got为system plt的地址,最后调用free即可

跟defcon ec3比,只不过这个有符号,但是给这个加了点限制,才能进入漏洞代码:

1、首先调用xnuca_timer,先得调用xnuca_set_timer初始化计时器
2、而进入计时器的初始化,需要State->cmd_9D0 & 1 == 1,那就需要通过xnuca_auth 5次后设置a1->cmd_9D0 |= 1u;
3、最后进入xnuca_timer中的漏洞代码,需要cmd_9D0 & 4 == 1,这个可以通过调用xnuca_send_request设置,不过也得必须调用xnuca_send_request来传递我们的参数

跟defcon ec3不一样的还有malloc的返回值不是0x7fxxxxxxx,所以指向直接fd劫持到got表,修改free了

强网杯2019线上赛qwct

题目:https://github.com/ray-cp/vm-escape/tree/master/qemu-escape/qwb-preliminary-2019-qwct

漏洞:

通过strlen()到\x00才截止可以越界读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v18 = strlen((const char *)opaque->crypto.output_buf);
v19 = size_copy == 1;
v20 = size_copy == 1;
if ( addr < v18 + 0x3000 && v19 )
{
if ( (opaque->crypto.statu - 6) & 0xFFFFFFFFFFFFFFFDLL )
{
result = -1LL;
}
else
{
v22 = *((_BYTE *)opaque + addr - 0x15C0);
result = v22;
}
return result;

aes_encrypt_function和aes_decrypto_function都有8字节溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  if ( v7 )
{
v9 = 0LL;
do
{
v10 = (__int64)&output_buf[v9];
v11 = (__int64)&v4[v9];
v9 += 16LL;
AES_ecb_encrypt(v11, v10, (__int64)&aes, 0LL);
}
while ( v7 > v9 );
v18 = 0LL;
v19 = 0;
v12 = 0;
for ( i = 0LL; ; v12 = *((_BYTE *)&v18 + (i & 7)) )
{
v14 = output_buf[i] ^ v12;
v15 = i++;
*((_BYTE *)&v18 + (v15 & 7)) = v14;
if ( v7 == i )
break;
}
v16 = v18;
}
else
{
v16 = 0LL;
}
*(_QWORD *)&output_buf[v7] = v16;
return 1;
}

漏洞利用:
1、mmio_write不能直接填充output_buf,所以我们通过调用stream_encrypto_fucntion去填充疑惑后都是非0的,那么strlen计算就会超过0x800,那就可以越界读,读取到函数指针stream_encrypto_fucntion的地址,从而算出程序的基址,及system_plt地址

2、虽然aes_encrypt_function和aes_decrypto_function都有8字节溢出,但是我们需要控制output_buf的值,我们才能最终控制计算出来的值(即循环异或,第一次是异或0,第二次是异或上一次的结果),直接通过aes_encrypt_function利用有点困哪,我们难以控制加密后的值。但是我们先aes加密,再通过aes解密,那么我们就可以精准控制解密结果了。

题外话

关注下有没常见套路整数溢出漏洞什么的

附录

关于exp传到qemu中

远程环境

  • telnet:telnet XXX.XXX.XXX.XXX 6666 > pwn.b64 && base64 -d pwn.b64 > pwn
  • 有无wget
  • 实在不行难道 echo?

本地环境

1、scp
2、sftp
3、文件系统打包

1
2
3
4
5
6
7
//解压
cpio -idmv < rootfs.img

//编译+打包
gcc -o exp -static exp.c
cp ./exp ./rootfs/root
cd ./rootfs && find . | cpio -o --format=newc > ../XXX

调用计算器,浏览器

1
2
3
4
// char *para="google-chrome –no-sandbox file:///home/qwb/Desktop/success.mp4";
// >>> from pwn import *
// >>> map(hex, unpack_many("gnome-calculator"))
// ['0x6d6f6e67', '0x61632d65', '0x6c75636c', '0x726f7461']

msfvenom -p linux/x64/exec cmd=”export DISPLAY=:0.0&&xcalc” -f dw

打赏专区